/*
This is part of Screenbar, which is based in part on the Litestep
shell source code.

Copyright (C) 2008-2009 Jim Babcock
Copyright (C) 1997-2009 The LiteStep Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "stdafx.h"

/////////////////////////////////////////////////////////////////////////////

bool LayoutLocation::operator<(const LayoutLocation &rhs) const
{
	if(element < rhs.element) return true;
	if(element > rhs.element) return false;
	if(context < rhs.context) return true;
	else return false;
}

bool LayoutLocation::operator==(const LayoutLocation &rhs) const
{
	return element == rhs.element && context.match(rhs.context);
}

/////////////////////////////////////////////////////////////////////////////

Layout::Layout(LayoutElement *rootElement, ElementContext rootContext, Rect boundingRect, string sizeFallbacks)
{
	this->rootElement = rootElement;
	this->rootContext = rootContext;
	this->boundingRect = boundingRect;
	this->sizeFallbacks = new SizeFallbackChain(sizeFallbacks);

	this->cachedRoot = NULL;
	this->prevFallback = NULL;
	this->fallbackUsed = 0;
	
	if(rootContext.vertical)
		this->rootContext.thickness = boundingRect.width;
	else
		this->rootContext.thickness = boundingRect.height;
}

Layout::~Layout()
{
	vector<LayoutCacheNode*> cacheNodes;
	
	if(cachedRoot)
	{
		cachedRoot->traverse(cacheNodes);
		
		for(unsigned ii=0; ii<cacheNodes.size(); ii++)
			delete cacheNodes[ii];
	}
}

void Layout::setRect(Rect boundingRect)
{
	this->boundingRect = boundingRect;
}

bool Layout::update()
{
	bool changed = false;
	
	//
	// If there is no previous layout to update, build from scratch.
	//
	if(!cachedRoot)
	{
		fallbackUsed = 0;
		SizeFallback *fallback = sizeFallbacks->getFallback(fallbackUsed);
		cachedRoot = buildElement(rootElement, &rootContext, fallback);
		changed = true;
	}
	else
	{
		//
		// If a lesser fallback level would work, reduce the fallback level and
		// rebuild the tree.
		//
		while(fallbackUsed > 0)
		{
			if(prevFallback) {
				updateTree(prevFallback, sizeFallbacks->getFallback(fallbackUsed-1));
			} else {
				prevFallback = buildElement(rootElement, &rootContext, sizeFallbacks->getFallback(fallbackUsed-1));
			}
			computeLengths(prevFallback);
			
			if(prevFallback->context.vertical && prevFallback->size.minimum > boundingRect.height)
				break;
			if(!prevFallback->context.vertical && prevFallback->size.minimum > boundingRect.width)
				break;
			
			fallbackUsed--;
			
			deleteTree(prevFallback);
			prevFallback = NULL;
			
			deleteTree(cachedRoot);
			cachedRoot = buildElement(rootElement, &rootContext, sizeFallbacks->getFallback(fallbackUsed));
			changed = true;
		}
		
		//
		// Update the tree structure and mark changed nodes
		//
		if(!changed) {
			SizeFallback *fallback = sizeFallbacks->getFallback(fallbackUsed);
			if(updateTree(cachedRoot, fallback))
				changed = true;
		}
	}
	
	//
	// Update element lengths
	//
	computeLengths(cachedRoot);
	
	SizeFallback *fallback = sizeFallbacks->getFallback(fallbackUsed);
	
	//
	// If the current fallback level isn't enough, go to the next one
	//
	while((!cachedRoot->context.vertical && (cachedRoot->size.minimum > boundingRect.width)
		|| cachedRoot->context.vertical && (cachedRoot->size.minimum > boundingRect.height))
		&& fallbackUsed+1 < sizeFallbacks->numFallbacks())
	{
		if(prevFallback) {
			deleteTree(prevFallback);
			prevFallback = NULL;
		}
		
		fallbackUsed++;
		fallback = sizeFallbacks->getFallback(fallbackUsed);
		deleteTree(cachedRoot);
		cachedRoot = buildElement(rootElement, &rootContext, fallback);
		computeLengths(cachedRoot);
		changed = true;
	}
	
	//
	// Update element positions
	// Also update the 'animating' flag with this traversal
	//
	cachedRoot->boundingRect.width = boundingRect.width;
	cachedRoot->boundingRect.height = boundingRect.height;
	this->animating = false;
	computePositions(cachedRoot);
	
	//
	// Update hover state
	//
	if(updateHover())
		changed = true;
	
	//printTree(cachedRoot, 0); //DEBUG
	
	return changed;
}

bool Layout::updateHover()
{
	if(!cachedRoot)
		return false;
	
	bool changed = false;
	SizeFallback *fallback = sizeFallbacks->getFallback(fallbackUsed);
	
	// For each element marked as hovered, check that it still is
	for(vector<LayoutCacheNode*>::iterator ii=hoveredNodes.begin(); ii!=hoveredNodes.end(); )
	{
		LayoutCacheNode *node = *ii;
		LayoutElement *element = node->element;
		
		if(!element->isHovered(node))
		{
			vector<LayoutCacheNode*> nodes;
			node->traverse(nodes);
			for(unsigned jj=0; jj<nodes.size(); jj++)
				nodes[jj]->context.hovered = false;
			
			ii = hoveredNodes.erase(hoveredNodes.begin());
			
			node->context.hovered = false;
			if(updateTree(node, fallback)) {
				computeLengths(node);
				computePositions(node);
				changed = true;
			}
		} else {
			ii++;
		}
	}
	
	// For each element under the cursor, if it's hoverable but not marked as
	// hovered, mark and update it
	LayoutCacheNode *pos = cachedRoot;
	Point cursorPos = getCursorPos();
	
	if(checkHover(cachedRoot, cursorPos, fallback))
		changed = true;
	
	return changed;
}

bool Layout::fallbackFits(int index)
{
	SizeFallback *fallback = sizeFallbacks->getFallback(index);
	LayoutCacheNode *tree = buildElement(rootElement, &rootContext, fallback);
	
	computeLengths(tree);
	int size = tree->size.minimum;
	
	deleteTree(tree);

	if(tree->context.vertical)
		return size <= boundingRect.height;
	return size <= boundingRect.width;
}

LayoutCacheNode *Layout::buildElement(LayoutElement *element, const ElementContext *context, SizeFallback *fallback)
{
	LayoutCacheNode *ret = new LayoutCacheNode(this, element, context, fallback);
	element->update(ret);
	
	vector<LayoutLocation> children;
	element->getChildren(ret, children);
	
	for(unsigned ii=0; ii<children.size(); ii++)
		children[ii].element = fallback->mapElement(children[ii].element);
	
	for(unsigned ii=0; ii<children.size(); ii++)
	{
		LayoutCacheNode *child = buildElement(children[ii].element, &children[ii].context, fallback);
		ret->children.push_back(child);
		child->parent = ret;
	}
	
	ret->changed = true;
	
	return ret;
}

void Layout::deleteTree(LayoutCacheNode *node)
{
	while(node->refcount > 0)
	{
		for(unsigned ii=0; ii<references.size(); ii++)
		{
			if(references[ii]->getNode() == node) {
				references[ii]->invalidate();
				break;
			}
		}
	}
	
	for(unsigned ii=0; ii<node->children.size(); ii++)
		deleteTree(node->children[ii]);
	
	for(vector<LayoutCacheNode*>::iterator ii=hoveredNodes.begin(); ii!=hoveredNodes.end(); )
	{
		if(*ii == node)
			ii = hoveredNodes.erase(ii);
		else
			ii++;
	}
	
	delete node;
}

bool Layout::updateTree(LayoutCacheNode *node, SizeFallback *fallback)
{
	bool changed = false;
	LayoutElement *element = node->element;
	
	// Check for changes to the element itself
	if(element->changed(node)) {
		element->update(node);
		changed = true;
	}
	
	// Check for changed children
	vector<LayoutLocation> newChildren;
	
	if(element->childrenCanChange)
	{
		element->getChildren(node, newChildren);
		for(unsigned ii=0; ii<newChildren.size(); ii++)
			newChildren[ii].element = fallback->mapElement(newChildren[ii].element);
		
		vector<LayoutCacheNode*> oldChildren = node->children;
		node->children.clear();
		
		if(newChildren.size() != oldChildren.size()) {
			changed = true;
		}
		
		for(unsigned ii=0; ii<oldChildren.size(); ii++)
			oldChildren[ii]->visited = false;
		
		int firstUnusedOld = 0;
		for(unsigned ii=0; ii<newChildren.size(); ii++)
		{
			bool reused = false;
			for(unsigned jj=firstUnusedOld; jj<oldChildren.size(); jj++)
			{
				if(matchNodes(oldChildren[jj], &newChildren[ii]))
				{
					node->children.push_back(oldChildren[jj]);
					if(updateTree(oldChildren[jj], fallback))
						changed = true;
					oldChildren[jj]->visited = true;
					reused = true;
					
					while(firstUnusedOld<(int)oldChildren.size() && oldChildren[firstUnusedOld]->visited)
						firstUnusedOld++;
					break;
				}
				else
				{
					// If we get here, it means either children have changed,
					// or they've changed *order*.
					changed = true;
				}
			}
			if(!reused)
			{
				LayoutCacheNode *newNode = buildElement(newChildren[ii].element, &newChildren[ii].context, fallback);
				node->children.push_back(newNode);
				newNode->parent = node;
			}
		}
		
		// Delete any children which were part of the old hierarchy but not the
		// new one
		for(unsigned ii=0; ii<oldChildren.size(); ii++) {
			if(!oldChildren[ii]->visited) {
				deleteTree(oldChildren[ii]);
				changed = true;
			}
		}
	}
	else
	{
		for(unsigned ii=0; ii<node->children.size(); ii++)
		{
			if(updateTree(node->children[ii], fallback))
				changed = true;
		}
	}
	
	node->changed = changed;
	return changed;
}

bool Layout::matchNodes(LayoutCacheNode *a, LayoutLocation *b)
{
	return a->element == b->element
	    && a->context.match(b->context);
}

void Layout::computeLengths(LayoutCacheNode *node)
{
	if(node->changed)
	{
		for(unsigned ii=0; ii<node->children.size(); ii++)
			computeLengths(node->children[ii]);
		node->size = node->element->getLength(node);
	}
}

void Layout::computePositions(LayoutCacheNode *node)
{
	int numChildren = node->children.size();
	
	if(node->element->animating)
		animating = true;
	
	vector<Rect> oldPositions(node->children.size());
	for(int ii=0; ii<numChildren; ii++)
		oldPositions[ii] = node->children[ii]->boundingRect;
	
	node->element->arrangeChildren(node, node->children);
	
	for(int ii=0; ii<numChildren; ii++) {
		LayoutCacheNode *child = node->children[ii];
		if(child->changed || oldPositions[ii]!=child->boundingRect)
			computePositions(child);
	}
}

bool Layout::checkHover(LayoutCacheNode *node, Point cursorPos, SizeFallback *sizeFallback)
{
	bool changed = false;
	
	if(node->element->hoverGroup)
	{
		if(!node->context.hovered)
		{
			vector<LayoutCacheNode*> nodes;
			node->traverse(nodes);
			for(unsigned ii=0; ii<nodes.size(); ii++)
				nodes[ii]->context.hovered = true;
			
			hoveredNodes.push_back(node);
			if(updateTree(node, sizeFallback))
			{
				changed = true;
				computeLengths(node);
				computePositions(node);
			}
		}
	}
	
	for(unsigned ii=0; ii<node->children.size(); ii++)
	{
		LayoutCacheNode *child = node->children[ii];
		if(child->boundingRect.containsPoint(cursorPos.x - boundingRect.left, cursorPos.y - boundingRect.top))
		{
			if(checkHover(child, cursorPos, sizeFallback))
				changed = true;
		}
	}
	
	return changed;
}

void Layout::draw(HDC drawContext)
{
	if(!cachedRoot)
		return;
	rootElement->draw(drawContext, cachedRoot);
}

LayoutReference *Layout::elementAtPoint(int x, int y)
{
	if(!cachedRoot)
		return NULL;
	
	LayoutCacheNode *node = cachedRoot->elementAtPoint(x, y);
	return new LayoutReference(this, node);
}

LayoutReference *Layout::getReferenceTo(LayoutCacheNode *node)
{
	return new LayoutReference(this, node);
}

LayoutReference *Layout::findNodeMatching(bool (*condition)(LayoutCacheNode*, void*), void *param)
{
	vector<LayoutCacheNode*> nodes;
	traverse(nodes);
	
	for(unsigned ii=0; ii<nodes.size(); ii++)
	{
		if(condition(nodes[ii], param))
			return getReferenceTo(nodes[ii]);
	}
	
	return NULL;
}

bool Layout::allowAutoHide()
{
	if(!cachedRoot)
		return true;
	
	vector<LayoutCacheNode*> nodes;
	traverse(nodes);
	
	for(unsigned ii=0; ii<nodes.size(); ii++) {
		if(!nodes[ii]->element->allowAutoHide(nodes[ii]))
			return false;
	}
	
	return true;
}

bool Layout::isAnimating()
{
	return animating;
}

void Layout::printTree()
{
	if(!cachedRoot) {
		trace << "[empty layout tree]\n";
	} else {
		printTree(cachedRoot, 0);
	}
}

void Layout::traverse(vector<LayoutCacheNode*> &visited)
{
	if(!cachedRoot)
		return;
	cachedRoot->traverse(visited);
}

ElementSize Layout::getLength()
{
	if(!cachedRoot)
		return ElementSize(0,0);
	
	return cachedRoot->element->getLength(cachedRoot);
}

void Layout::printTree(LayoutCacheNode *node, int indentLevel)
{
	for(int ii=0; ii<indentLevel; ii++)
		trace << "\t";
	trace << node->element->prefix << "(length:" << node->size.minimum << "-" << node->size.preferred << ") (rect:" << node->boundingRect << ")\n";
	
	for(unsigned ii=0; ii<node->children.size(); ii++)
		printTree(node->children[ii], indentLevel+1);
}

/////////////////////////////////////////////////////////////////////////////

LayoutReference::LayoutReference(Layout *layout, LayoutCacheNode *node)
{
	this->layout = layout;
	this->node = node;
	node->refcount++;
	layout->references.push_back(this);
}

LayoutReference::~LayoutReference()
{
	if(node)
		invalidate();
}

LayoutCacheNode *LayoutReference::getNode()
{
	return node;
}

Layout *LayoutReference::getLayout()
{
	return layout;
}

void LayoutReference::invalidate()
{
	if(node) node->refcount--;
	node = NULL;

	for(unsigned ii=0; ii<layout->references.size(); ii++)
	{
		if(layout->references[ii] == this) {
			layout->references.erase(layout->references.begin()+ii);
			break;
		}
	}
}

/////////////////////////////////////////////////////////////////////////////

ElementContext::ElementContext()
{ }
ElementContext::ElementContext(ScreenbarWindow *window, Monitor *monitor,
	VirtualDesktop *desk, HWND task, bool vertical)
{
	this->window = window;
	this->monitor = monitor;
	this->desk = desk;
	this->taskWindow = task;
	this->hovered = false;
	this->vertical = vertical;
	this->thickness = 0;
}

WindowData *ElementContext::getTask() const
{
	return windowTracker->getWindow(taskWindow);
}

bool ElementContext::operator<(const ElementContext &rhs) const
{
	if(window < rhs.window) return true;
	if(window > rhs.window) return false;
	if(desk < rhs.desk) return true;
	if(desk > rhs.desk) return false;
	if(taskWindow < rhs.taskWindow) return true;
	else return false;
}

bool ElementContext::match(const ElementContext &rhs) const
{
	return window==rhs.window
	    && desk==rhs.desk
	    && taskWindow==rhs.taskWindow;
}

/////////////////////////////////////////////////////////////////////////////

LayoutElement::LayoutElement(string prefix)
	:prefix(prefix)
{
	onPress[mouseLeft]   = getConfigLine("OnLeftPress", "", prefix.c_str());
	onPress[mouseRight]  = getConfigLine("OnRightPress", "", prefix.c_str());
	onPress[mouseMiddle] = getConfigLine("OnMiddlePress", "", prefix.c_str());
	onRelease[mouseLeft]  = getConfigLine("OnLeftRelease", "", prefix.c_str());
	onRelease[mouseRight]  = getConfigLine("OnRightRelease", "", prefix.c_str());
	onRelease[mouseMiddle] = getConfigLine("OnMiddleRelease", "", prefix.c_str());
	onClick[mouseLeft]   = getConfigLine("OnLeftClick",  "", prefix.c_str());
	onClick[mouseRight]  = getConfigLine("OnRightClick", "", prefix.c_str());
	onClick[mouseMiddle] = getConfigLine("OnMiddleClick", "", prefix.c_str());
	
	toolTip = getConfigLine("ToolTip", "", prefix.c_str());
	
	hoverGroup = getConfigBool("HoverGroup", false, prefix.c_str());
	draggable = getConfigBool("Draggable", false, prefix.c_str());
	isMinimizeTarget = getConfigBool("IsMinimizeTarget", false, prefix.c_str());
	
	string dragElementName = getConfigLine("DragElement", "", prefix.c_str());
	if(dragElementName == prefix)
		dragElementName = "";
	string placeholderName = getConfigLine("DragPlaceholder", "", prefix.c_str());
	if(placeholderName == prefix)
		placeholderName = "";
	dragElement = layoutPool->getElement(dragElementName);
	dragPlaceholder = layoutPool->getElement(placeholderName);
	
	this->childrenCanChange = false;
	this->animating = false;
}

LayoutElement::~LayoutElement()
	{ }

string LayoutElement::toString() const
{
	return prefix;
}

bool LayoutElement::isExpanding()
	{ return false; }

bool LayoutElement::changed(const LayoutCacheNode *context)
{
	return false;
}

void LayoutElement::update(LayoutCacheNode *node)
{
}

void LayoutElement::deleteElementData(LayoutCacheNode *node, void *elementData)
{
}

ElementSize LayoutElement::getLength(const LayoutCacheNode *node)
{
	ElementSize ret;
	ret.minimum = 0;
	ret.preferred = 0;
	
	for(unsigned ii=0; ii<node->children.size(); ii++)
	{
		LayoutCacheNode *child = node->children[ii];
		if(child->size.minimum > ret.minimum)
			ret.minimum = child->size.minimum;
		if(child->size.preferred > ret.preferred)
			ret.preferred = child->size.preferred;
	}
	
	return ret;
}

void LayoutElement::getChildren(LayoutCacheNode *node, vector<LayoutLocation> &outChildren)
{
}

void LayoutElement::arrangeChildren(LayoutCacheNode *container, vector<LayoutCacheNode*> &children)
{
	for(unsigned ii=0; ii<children.size(); ii++)
		children[ii]->boundingRect = container->boundingRect;
}

void LayoutElement::draw(HDC drawContext, const LayoutCacheNode *node)
{
	for(unsigned ii=0; ii<node->children.size(); ii++)
	{
		LayoutCacheNode *child = node->children[ii];
		child->element->draw(drawContext, child);
	}
}

bool LayoutElement::isHovered(LayoutCacheNode *node)
{
	ScreenbarWindow *window = node->context.window;
	
	if(window)
	{
		Point mousePos = getCursorPos();
		
		// Convert mouse pos to window coordinates
		mousePos -= window->getTopLeft();
		
		if(node->boundingRect.containsPoint(mousePos.x, mousePos.y))
			return true;
	}
	return false;
}

bool LayoutElement::allowAutoHide(LayoutCacheNode *node)
{
	return true;
}

/////////////////////////////////////////////////////////////////////////////

SubstituteElement::SubstituteElement(LayoutElement *element)
	:LayoutElement(element->prefix)
{
	this->child = element;
}

LayoutElement *SubstituteElement::removeSubstitute()
{
	return child;
}
